﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Threading;
using System.Net;
using System.Web.Script.Serialization;
using System.Text;

namespace AzureTosser.Services.POCImplementations
{
    public class POCTossBus: ITossBus
    {
        private class TossBody
        {
            public string user { get; set; }
            public string text { get; set; }
        }
        private class PipeUnit
        {
            public PipeDefinition Definition { get; set; }
            public IPipe IPipe { get; set; }
            public AutoResetEvent WaitLock { get; set; }
        }
        private Dictionary<string, PipeUnit> mReceivers = new Dictionary<string, PipeUnit>();
        private Dictionary<string, PipeUnit> mSenders = new Dictionary<string, PipeUnit>();

        public string RegisterReceiverPipe(string scope, string topic)
        {
            if (!TopicHelper.IsValidTopic(scope))
                throw new ArgumentException("Invalid scope format.");
            if (!TopicHelper.IsValidTopic(topic))
                throw new ArgumentException("Invalid topic format.");
            PipeDefinition def = new PipeDefinition 
            {
                PipeType = PipeType.Receive, 
                AllowedTopics = new List<string>{topic},
                Scope = scope
            };
            return RegisterPipe(def);
        }

        public string RegisterSenderPipe(PipeType type, string scope)
        {
            if (!TopicHelper.IsValidTopic(scope))
                throw new ArgumentException("Invalid scope format.");
            switch (type)
            {
                case PipeType.MulticastSend:
                case PipeType.UnicastSend:
                    PipeDefinition def = new PipeDefinition {PipeType = type, Scope=scope};
                    return RegisterPipe(def);
                case PipeType.MulticastDuplex:
                    PipeDefinition mdef = new PipeDefinition { PipeType = PipeType.MulticastSend, Scope = scope };
                    var mid = RegisterPipe(mdef);
                    PipeDefinition rdef = new PipeDefinition { PipeType = PipeType.Receive, Scope = scope };
                    rdef.Id = mid;
                    return RegisterPipe(rdef);
                case PipeType.UnicastDuplex:
                    PipeDefinition udef = new PipeDefinition { PipeType = PipeType.UnicastSend, Scope = scope };
                    var rid = RegisterPipe(udef);
                    PipeDefinition rudef = new PipeDefinition { PipeType = PipeType.Receive, Scope = scope };
                    rudef.Id = rid;
                    return RegisterPipe(rudef);
                default:
                    throw new ArgumentException("Unsupported pipe type.");
            }
        }

        public string RegisterPipe(PipeDefinition pipeDefinition)
        {
            if (pipeDefinition == null)
                throw new ArgumentNullException("Pipe Definition is null.");
            if (string.IsNullOrEmpty(pipeDefinition.Id))
                pipeDefinition.Id = Guid.NewGuid().ToString();
            switch (pipeDefinition.PipeType)
            {
                case PipeType.MulticastSend:
                case PipeType.UnicastSend:
                    ISendPipe spipe = new POCSendPipe(pipeDefinition.Id);
                    spipe.MessageAdded += new EventHandler<MessageEventArgs>(sendPipe_MessageAdded);
                    mSenders.Add(pipeDefinition.Id, new PipeUnit { Definition = pipeDefinition, IPipe = spipe, WaitLock = new AutoResetEvent(false) });
                    break;
                case PipeType.Receive:
                    IReceivePipe pipe = new POCReceivePipe(pipeDefinition.Id);
                    pipe.MessageAdded += new EventHandler<MessageEventArgs>(receivePipe_MessageAdded);
                    mReceivers.Add(pipeDefinition.Id, new PipeUnit { Definition = pipeDefinition, IPipe = pipe, WaitLock = new AutoResetEvent(false) });
                    break;
                default:
                    throw new ArgumentOutOfRangeException(string.Format("Pipe Type '{0}' not supported.", pipeDefinition.PipeType.ToString()));
            }
            return pipeDefinition.Id;
        }

        void sendPipe_MessageAdded(object sender, MessageEventArgs e)
        {
            var receivers = from p in mReceivers.Values
                             where p.Definition.Scope == e.Message.Scope
                                   && (p.Definition.AllowedTopics == null || p.Definition.AllowedTopics.IndexOf(e.Message.Topic) >= 0)
                             select p.IPipe;
            foreach (var receiver in receivers)
            {
                receiver.Push(e.Message);
            }
        }

        void receivePipe_MessageAdded(object sender, MessageEventArgs e)
        {
            var pipe = sender as IReceivePipe;
            if (pipe != null)
            {
                var reg = mReceivers.Values.FirstOrDefault(p => p.IPipe.Id == pipe.Id);
                reg.WaitLock.Set();
            }
        }
        public PipeDefinition GetPipeDefinition(string pipeId, bool receive)
        {
            if (receive && mReceivers.ContainsKey(pipeId))
                return mReceivers[pipeId].Definition;
            else if (mSenders.ContainsKey(pipeId))
                return mSenders[pipeId].Definition;
            else
                return null;
        }
        public void Send(PipeDefinition pipe, string topic, string message)
        {
            var sendPipe = getPipe(pipe.Id, false);
            if (sendPipe == null || !(sendPipe is ISendPipe))
                throw new ArgumentException("Pipe not found or pipe is of wrong type.");
            sendPipe.Push(new Message { Scope = pipe.Scope, Topic = topic, Body = message });
        }
        private IPipe getPipe(string pipeId, bool receive)
        {
            if (receive && mReceivers.ContainsKey(pipeId))
                return mReceivers[pipeId].IPipe;
            else if (mSenders.ContainsKey(pipeId))
                return mSenders[pipeId].IPipe;
            else
                return null;
        }
        public Message Receive(string pipeId, string topic)
        {
            if (mReceivers.ContainsKey(pipeId))
            {
                var msg = mReceivers[pipeId].IPipe.Peek();
                if (msg != null)
                    return mReceivers[pipeId].IPipe.Pop();
                else
                    mReceivers[pipeId].WaitLock.WaitOne(new TimeSpan(0, 0, 30));
                msg = mReceivers[pipeId].IPipe.Peek();
                if (msg != null)
                    return mReceivers[pipeId].IPipe.Pop();
                else
                    return null;
            }
            else
                throw new ArgumentException("Pipe doesn't exist.");
        }
    }
}